package hn.sigit.logic.viewer.toolbox;

import hn.sigit.dao.hnd.cadastre.HND_SpatialZoneDAO;
import hn.sigit.logic.geometry.GeometryOperations;
import hn.sigit.logic.viewer.InteractiveViewerHelper;
import hn.sigit.logic.viewer.toolbox.BorderHelper.Coord;
import hn.sigit.model.commons.IParcel;
import hn.sigit.model.commons.IProperty;
import hn.sigit.model.commons.ISpatialZone;
import hn.sigit.model.hnd.administrative.HND_RuleOperatorType;
import hn.sigit.model.hnd.cadastre.HND_Layer;
import hn.sigit.model.hnd.cadastre.HND_SpatialZone;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;

import com.vividsolutions.jts.geom.Coordinate;
import com.vividsolutions.jts.geom.Geometry;
import com.vividsolutions.jts.geom.LinearRing;
import com.vividsolutions.jts.geom.MultiPolygon;
import com.vividsolutions.jts.geom.Polygon;
import com.vividsolutions.jts.io.WKTWriter;


public class CreateZoneViewerCallable implements ViewerCallable, Serializable {
	private static final long serialVersionUID = 1L;
	
	@Override
	public void afterMerge(IParcel parcel1, IProperty property1,
			IParcel parcel2, IProperty property2, Polygon newShape,
			Date timeStamp) {
		throw new UnsupportedOperationException();
	}

	@Override
	public void doBorderOperation(
			InteractiveViewerHelper interactiveViewerHelper) {
		HND_Layer workingLayer = interactiveViewerHelper.getSelectedLayer();
		BorderHelper bh = interactiveViewerHelper.getBorderHelper();
		
		Coordinate[] coords = new Coordinate[bh.getCurrentBorder().size() + 1];
		for (int i = 0; i < bh.getCurrentBorder().size(); i++) {
			Coord c = bh.getCurrentBorder().get(i);
			coords[i] = new Coordinate(c.getX(), c.getY());
		}
		coords[bh.getCurrentBorder().size()] = new Coordinate(coords[0]);
		
		LinearRing lr = GeometryOperations.geomFactory.createLinearRing(coords);
		Polygon initialPoly = GeometryOperations.geomFactory.createPolygon(lr, null);

		//check if initial polygon is ok
		checkPolygon(initialPoly, interactiveViewerHelper);
		
		Geometry newShape;
		
		//obtain all polygons touching out initial polygon (p0)
		List<HND_SpatialZone> touchingSpList = HND_SpatialZoneDAO.loadSpatialZonesByLayerAndGeom(
				workingLayer, initialPoly, HND_RuleOperatorType.DISTANCE);
		
		WKTWriter wktWriter = new WKTWriter();
		if (touchingSpList.size() > 0) {
			//obtain union of all touching zones plus p0
			Geometry gUnion = (Geometry) initialPoly.clone();
			for (ISpatialZone sz : touchingSpList) {
				Geometry geom = sz.getShape();
				if (geom != null && geom instanceof Polygon)
					gUnion = gUnion.union(sz.getShape());
			}
			System.out.println("gUnion: " + wktWriter.write(gUnion));
			
			Geometry allEnvelope = gUnion.getEnvelope();
			List<HND_SpatialZone> allTouchingSpList = HND_SpatialZoneDAO.loadSpatialZonesByLayerAndGeom(
					workingLayer, allEnvelope, HND_RuleOperatorType.DISTANCE);
			
			Geometry gAllUnion = (Geometry) initialPoly.clone();
			Geometry gAllRest = GeometryOperations.geomFactory.createPolygon(null, null);
			for (ISpatialZone sz : allTouchingSpList) {
				Geometry geom = sz.getShape();
				if (geom != null && geom instanceof Polygon)
					gAllRest = gAllRest.union(sz.getShape());
			}
			gAllRest = gAllRest.difference(lr);
			gAllUnion = initialPoly.union(gAllRest);
			System.out.println("gAllUnion: " + wktWriter.write(gAllUnion));
			System.out.println("gAllRest: " + wktWriter.write(gAllRest));
			
			//eliminate any internal rings (holes)
			if (gAllUnion instanceof Polygon) {
				Polygon allPolygon = polygonWithoutHoles((Polygon) gAllUnion);
				gAllUnion = allPolygon;
			}
			else if (gAllUnion instanceof MultiPolygon) {
				MultiPolygon allMultiPolygon = multiPolygonWithoutHoles((MultiPolygon) gAllUnion);
				gAllUnion = allMultiPolygon;
			}
			
			//Calculate the difference between allPolygon and original spatial zones
			newShape = gAllUnion.difference(gAllRest);
			System.out.println("newShape: " + wktWriter.write(newShape));
		}
		else
			newShape = initialPoly;
		
		//check if new shape is ok
		checkPolygon(newShape, interactiveViewerHelper);

		if (newShape instanceof Polygon) {
			interactiveViewerHelper.getCreatedZoneGeoms().add(newShape);
			bh.clearBoundary();
		}
	}
	
	private Polygon polygonWithoutHoles(Polygon polygon) {
		if (polygon.getNumInteriorRing() > 0) {
			LinearRing extRing = GeometryOperations.geomFactory.createLinearRing(polygon.getExteriorRing().getCoordinates());
			polygon = GeometryOperations.geomFactory.createPolygon(extRing, null);
		}
		return polygon;
	}
	
	private MultiPolygon multiPolygonWithoutHoles(MultiPolygon multiPolygon) {
		boolean hasHoles = false;
		Polygon poly;
		for (int i = 0; i < multiPolygon.getNumGeometries(); i++) {
			poly = (Polygon) multiPolygon.getGeometryN(i);
			hasHoles = poly.getNumInteriorRing() > 0;
			if (hasHoles) break;
		}
		
		if (hasHoles) {
			List<Polygon> polyList = new ArrayList<Polygon>();
			for (int i = 0; i < multiPolygon.getNumGeometries(); i++) {
				poly = (Polygon) multiPolygon.getGeometryN(i);
				poly = polygonWithoutHoles(poly);
				polyList.add(poly);
			}
			Polygon[] polygons = polyList.toArray(new Polygon[0]);
			MultiPolygon mp = GeometryOperations.geomFactory.createMultiPolygon(polygons);
			
			return mp;
		}

		return multiPolygon;
	}
	
	private void checkPolygon(Geometry geom, InteractiveViewerHelper ivh) {
		if (!(geom instanceof Polygon) || !((Polygon) geom).isValid() || !((Polygon) geom).isSimple())
			throw new IllegalStateException(ivh.getResBundle().loadMessage("dataentry.create_zone.no_valid_geom"));
	}
}
